import PortalPuckMesh from "../mesh/PortalPuckMesh"
import * as THREE from "three"
import ObjectPool from "../observer/object.pool"
import Comparator from "../utils/Comparator"
import { PortalClickMessage } from "../message/portal.message"
import meshStateEnum from "../enum/meshState.enum"
import { ClickEvent, HoverEvent, UnhoverEvent } from "../event/GestureEvent"
import { Subscription } from "../utils/Subscription"
import InputIniModule, { InputSubscription } from "../module/InputIniModule"
import WebGLScene from "../three/WebGLScene"
import { RenderLayer } from "../utils/RenderLayers"
import CameraData from "../data/CameraData"
import { portalItem, PortalViewportLoader } from "../module/PortalPuckMeshModule"
import EngineContext from "../EngineContext"

export default class PortalPuckRenderer {
  container: THREE.Object3D
  bindings: (Subscription | InputSubscription)[]
  visibilityFilter: (e: any) => number
  scene: WebGLScene
  input: InputIniModule
  layer: RenderLayer
  cameraData: CameraData
  viewportLoader: PortalViewportLoader
  meshes: PortalPuckMesh[]
  activeMeshes: Record<string, boolean>
  meshToDataMap: Record<string, portalItem>
  dataToMeshMap: Record<string, PortalPuckMesh>
  activeTexturePromises: Record<string, Promise<THREE.Texture>>
  meshPool: ObjectPool<PortalPuckMesh>
  broadcast: EngineContext["broadcast"]
  constructor(e, t, i, n, a) {
    this.container = new THREE.Object3D()
    this.bindings = []
    this.visibilityFilter = e => meshStateEnum.HIDE
    this.scene = e
    this.input = t
    this.layer = n
    this.cameraData = a
    this.viewportLoader = i
    this.meshes = []
    this.activeMeshes = {}
    this.meshToDataMap = {}
    this.dataToMeshMap = {}
    this.activeTexturePromises = {}
    this.meshPool = new ObjectPool()
  }
  addPortal(e: portalItem) {
    let t: PortalPuckMesh
    const i = this.meshPool.get()
    i ? ((t = i.object), t.update(e)) : (t = this.meshPool.add(new PortalPuckMesh(e, this.layer)).object)
    this.meshes.push(t)
    this.container.add(t)
    this.meshToDataMap[t.id] = e
    this.dataToMeshMap[e.index] = t
    this.activateMesh(t, this.visibilityFilter(e))
  }
  removePortal(e: portalItem) {
    const t = this.dataToMeshMap[e.index]
    if (t) {
      this.meshPool.free(t)
      const i = this.meshes.findIndex(e => e.id === t.id)
      this.meshes.splice(i, 1)
      this.container.remove(t)
      this.deactivateMesh(t)
      delete this.meshToDataMap[t.id]
      delete this.dataToMeshMap[e.index]
    }
  }
  init() {}
  activate(e: EngineContext) {
    this.broadcast = e.broadcast.bind(e)
    this.bindings.push(
      this.input.registerMeshHandler(HoverEvent, Comparator.isType(PortalPuckMesh), (e, t) => {
        this.onPuckSelect(t)
      }),
      this.input.registerMeshHandler(UnhoverEvent, Comparator.isType(PortalPuckMesh), (e, t) => {
        this.onPuckDeselect(t)
      }),
      this.input.registerMeshHandler(ClickEvent, Comparator.isType(PortalPuckMesh), (e, t) => {
        this.onPuckClick(t)
      })
    )
    this.scene.add(this.container)
  }
  dispose() {
    for (const e of Object.keys(this.meshToDataMap)) {
      const t = this.meshToDataMap[e]
      const i = this.dataToMeshMap[t.index]
      this.removePortal(t)
      i.material.dispose()
      i.geometry.dispose()
    }
  }
  deactivate(e) {
    for (const e of this.bindings) e.cancel()
    this.bindings.length = 0
    this.scene.remove(this.container)
  }
  render(e: number) {
    for (const t of this.meshes) t.render(e, this.cameraData)
  }
  filter(e) {
    this.visibilityFilter = e
    for (const t of this.meshes) {
      const i = e(this.meshToDataMap[t.id])
      i === meshStateEnum.HIDE ? this.deactivateMesh(t) : this.activateMesh(t, i)
    }
  }
  mapSweepToMesh(e: string) {
    for (const t in this.meshToDataMap) {
      const i = this.meshToDataMap[t]
      if (i.toSweep.id === e && i.toExterior && i.fromInterior) return this.dataToMeshMap[i.index]
    }
    return null
  }
  activateMesh(e: PortalPuckMesh, t = 0) {
    this.activeMeshes[e.id] || (this.input.registerMesh(e, !1), (this.activeMeshes[e.id] = !0))
    e.setState(t)
  }
  deactivateMesh(e: PortalPuckMesh) {
    this.input.unregisterMesh(e)
    this.activeMeshes[e.id] = !1
    this.onPuckDeselect(e)
    e.setState(meshStateEnum.HIDE)
  }
  onPuckClick(e: PortalPuckMesh) {
    this.broadcast(new PortalClickMessage(this.meshToDataMap[e.id]))
  }
  onPuckSelect(e: PortalPuckMesh) {
    const t = this.meshToDataMap[e.id].toSweep
    const i = this.viewportLoader.getPortalTexture(t, t.rotation, e.position)
    this.activeTexturePromises[e.id] = i
    i.then(t => {
      this.activeTexturePromises.hasOwnProperty(e.id) && i === this.activeTexturePromises[e.id] && (e.updatePortalTexture(t), e.setHover(!0))
    })
  }
  onPuckDeselect(e: PortalPuckMesh) {
    e.setHover(!1)
    this.viewportLoader.releasePortalTexture(this.meshToDataMap[e.id].toSweep.id)
    delete this.activeTexturePromises[e.id]
  }
}
